/*
Copyright (C) 1996-1997 Id Software, Inc.

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 3
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

*/

#include "quakedef.h"

#pragma comment (lib, "dsound.lib")

// 64K is > 1 second at 16-bit, 22050 Hz
#define SECONDARY_BUFFER_SIZE	0x10000

typedef enum {SIS_SUCCESS, SIS_FAILURE, SIS_NOTAVAIL} sndinitstat;

static BOOL	dsound_init;
static BOOL	snd_firsttime = TRUE, snd_isdirect;

static int	sample16;
static int	snd_sent, snd_completed;


/*
 * Global variables. Must be visible to window-procedure function
 *  so it can unlock and free the data block after it has been played.
 */

HPSTR		lpData, lpData2;
DWORD		ds_BufferSize;
MMTIME		mmstarttime;

IDirectSound8 *ds_Object;
IDirectSoundBuffer *ds_Buffer;

sndinitstat SNDDMA_InitDirect (void);


/*
==================
S_BlockSound
==================
*/
void S_BlockSound (void)
{
	// DirectSound takes care of blocking itself
}


/*
==================
S_UnblockSound
==================
*/
void S_UnblockSound (void)
{
	// DirectSound takes care of blocking itself
}


/*
==================
FreeSound
==================
*/
void FreeSound (void)
{
	if (ds_Buffer)
	{
		ds_Buffer->Stop ();
		ds_Buffer->Release ();
	}

	if (ds_Object)
	{
		ds_Object->SetCooperativeLevel (vid.Window, DSSCL_NORMAL);
		ds_Object->Release ();
	}

	ds_Object = NULL;
	ds_Buffer = NULL;

	lpData = lpData2 = NULL;
	dsound_init = FALSE;
}


void SND_SetWaveFormat (WAVEFORMATEX *format, int channels, int samplebits, int speed)
{
	memset (format, 0, sizeof (WAVEFORMATEX));

	format->wFormatTag = WAVE_FORMAT_PCM;
	format->nChannels = channels;
	format->wBitsPerSample = samplebits;
	format->nSamplesPerSec = speed;
	format->nBlockAlign = format->nChannels * format->wBitsPerSample / 8;
	format->cbSize = 0;	// this is for extra info (so why not cbExtra?) and per MSDN should be 0 and is ignored for PCM anyway
	format->nAvgBytesPerSec = format->nSamplesPerSec * format->nBlockAlign;
}


/*
==================
SNDDMA_InitDirect

Direct-Sound support
==================
*/
sndinitstat SNDDMA_InitDirect (void)
{
	DSBUFFERDESC	dsbuf;
	DSBCAPS			dsbcaps;
	DWORD			dwSize, dwWrite;
	DSCAPS			dscaps;
	WAVEFORMATEX	format;
	HRESULT			hresult;
	int				reps;

	memset ((void *) &sn, 0, sizeof (sn));

	shm = &sn;

	shm->channels = 2;
	shm->samplebits = 16;
	shm->speed = 11025;

	SND_SetWaveFormat (&format, shm->channels, shm->samplebits, shm->speed);

	while ((hresult = DirectSoundCreate8 (NULL, &ds_Object, NULL)) != DS_OK)
	{
		if (hresult != DSERR_ALLOCATED)
		{
			Con_Printf (PRINT_SAFE, "DirectSound create failed\n");
			return SIS_FAILURE;
		}

		if (MessageBox (NULL,
						"The sound hardware is in use by another app.\n\n"
						"Select Retry to try to start sound again or Cancel to run Quake with no sound.",
						"Sound not available",
						MB_RETRYCANCEL | MB_SETFOREGROUND | MB_ICONEXCLAMATION) != IDRETRY)
		{
			Con_Printf (PRINT_SAFE, "DirectSoundCreate failure\n"
							"  hardware already in use\n");
			return SIS_NOTAVAIL;
		}
	}

	dscaps.dwSize = sizeof (dscaps);

	if (ds_Object->GetCaps (&dscaps) != DS_OK)
	{
		Con_Printf (PRINT_SAFE, "Couldn't get DS caps\n");
	}

	if (dscaps.dwFlags & DSCAPS_EMULDRIVER)
	{
		Con_Printf (PRINT_SAFE, "No DirectSound driver installed\n");
		FreeSound ();
		return SIS_FAILURE;
	}

	if (ds_Object->SetCooperativeLevel (vid.Window, DSSCL_EXCLUSIVE) != DS_OK)
	{
		Con_Printf (PRINT_SAFE, "Set coop level failed\n");
		FreeSound ();
		return SIS_FAILURE;
	}

	memset (&dsbcaps, 0, sizeof (dsbcaps));
	dsbcaps.dwSize = sizeof (dsbcaps);

	// create the secondary buffer we'll actually work with
	memset (&dsbuf, 0, sizeof (dsbuf));
	dsbuf.dwSize = sizeof (DSBUFFERDESC);
	dsbuf.dwFlags = DSBCAPS_CTRLFREQUENCY | DSBCAPS_LOCSOFTWARE;
	dsbuf.dwBufferBytes = SECONDARY_BUFFER_SIZE;
	dsbuf.lpwfxFormat = &format;

	if (ds_Object->CreateSoundBuffer (&dsbuf, &ds_Buffer, NULL) != DS_OK)
	{
		Con_Printf (PRINT_SAFE, "DS:CreateSoundBuffer Failed");
		FreeSound ();
		return SIS_FAILURE;
	}

	shm->channels = format.nChannels;
	shm->samplebits = format.wBitsPerSample;
	shm->speed = format.nSamplesPerSec;

	memset (&dsbcaps, 0, sizeof (dsbcaps));
	dsbcaps.dwSize = sizeof (dsbcaps);

	if (ds_Buffer->GetCaps (&dsbcaps) != DS_OK)
	{
		Con_Printf (PRINT_SAFE, "DS:GetCaps failed\n");
		FreeSound ();
		return SIS_FAILURE;
	}

	if (snd_firsttime)
		Con_Printf (PRINT_SAFE, "Using secondary sound buffer\n");

	// Make sure mixer is active
	ds_Buffer->Play (0, 0, DSBPLAY_LOOPING);

	if (snd_firsttime)
		Con_Printf (PRINT_SAFE, "   %d channel(s)\n"
						"   %d bits/sample\n"
						"   %d bytes/sec\n",
						shm->channels, shm->samplebits, shm->speed);

	ds_BufferSize = dsbcaps.dwBufferBytes;

	// initialize the buffer
	reps = 0;

	while ((hresult = ds_Buffer->Lock (0, ds_BufferSize, (void **) &lpData, &dwSize, NULL, NULL, 0)) != DS_OK)
	{
		if (hresult != DSERR_BUFFERLOST)
		{
			Con_Printf (PRINT_SAFE, "SNDDMA_InitDirect: DS::Lock Sound Buffer Failed\n");
			FreeSound ();
			return SIS_FAILURE;
		}

		if (++reps > 10000)
		{
			Con_Printf (PRINT_SAFE, "SNDDMA_InitDirect: DS: couldn't restore buffer\n");
			FreeSound ();
			return SIS_FAILURE;
		}
	}

	memset (lpData, 0, dwSize);
	//		lpData[4] = lpData[5] = 0x7f;	// force a pop for debugging

	ds_Buffer->Unlock (lpData, dwSize, NULL, 0);

	/* we don't want anyone to access the buffer directly w/o locking it first. */
	lpData = NULL;

	ds_Buffer->Stop ();
	ds_Buffer->GetCurrentPosition (&mmstarttime.u.sample, &dwWrite);
	ds_Buffer->Play (0, 0, DSBPLAY_LOOPING);

	shm->soundalive = TRUE;
	shm->splitbuffer = FALSE;
	shm->samples = ds_BufferSize / (shm->samplebits / 8);
	shm->samplepos = 0;
	shm->submission_chunk = 1;
	shm->buffer = (unsigned char *) lpData;
	sample16 = (shm->samplebits / 8) - 1;

	dsound_init = TRUE;

	return SIS_SUCCESS;
}


/*
==================
SNDDMA_Init

Try to find a sound device to mix for.
Returns FALSE if nothing is found.
==================
*/

int SNDDMA_Init (void)
{
	sndinitstat	stat;
	dsound_init = 0;

	stat = SIS_FAILURE;	// assume DirectSound won't initialize

	// Init DirectSound
	if (snd_firsttime || snd_isdirect)
	{
		stat = SNDDMA_InitDirect ();

		if (stat == SIS_SUCCESS)
		{
			snd_isdirect = TRUE;

			if (snd_firsttime)
				Con_Printf (PRINT_SAFE, "DirectSound initialized\n");
		}
		else
		{
			snd_isdirect = FALSE;
			Con_Printf (PRINT_SAFE, "DirectSound failed to init\n");
		}
	}

	snd_firsttime = FALSE;

	if (!dsound_init)
	{
		if (snd_firsttime)
			Con_Printf (PRINT_SAFE, "No sound device initialized\n");

		return 0;
	}

	return 1;
}

/*
==============
SNDDMA_GetDMAPos

return the current sample position (in mono samples read)
inside the recirculating dma buffer, so the mixing code will know
how many sample are required to fill it up.
===============
*/
int SNDDMA_GetDMAPos (void)
{
	MMTIME	mmtime;
	int		s;
	DWORD	dwWrite;

	if (dsound_init)
	{
		mmtime.wType = TIME_SAMPLES;
		ds_Buffer->GetCurrentPosition (&mmtime.u.sample, &dwWrite);
		s = mmtime.u.sample - mmstarttime.u.sample;
	}
	else return 0;

	s >>= sample16;
	s &= (shm->samples - 1);

	return s;
}

/*
==============
SNDDMA_Submit

Send sound to device if buffer isn't really the dma buffer
===============
*/
void SNDDMA_Submit (void)
{
}


/*
==============
SNDDMA_Shutdown

Reset the sound device for exiting
===============
*/
void SNDDMA_Shutdown (void)
{
	FreeSound ();
}

